<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  <title>sunJsona的个人博客</title>

  <!-- keywords -->
  

  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta property="og:type" content="website">
<meta property="og:title" content="sunJsona的个人博客">
<meta property="og:url" content="https://xiaochangzai.github.io/page/4/index.html">
<meta property="og:site_name" content="sunJsona的个人博客">
<meta property="og:locale" content="Simple Chinese">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="sunJsona的个人博客">
  
    <link rel="alternative" href="/atom.xml" title="sunJsona的个人博客" type="application/atom+xml">
  
  
    <link rel="icon" href="http://7xkj1z.com1.z0.glb.clouddn.com/head.jpg">
  
  <link rel="stylesheet" href="/css/style.css">
  
  

  <script src="//cdn.bootcss.com/require.js/2.3.2/require.min.js"></script>
  <script src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>

  
</head>
<body>
  <div id="container">
    <div id="particles-js"></div>
    <div class="left-col">
    <div class="overlay"></div>
<div class="intrude-less">
	<header id="header" class="inner">
		<a href="/" class="profilepic">
			
			<img lazy-src="http://7xkj1z.com1.z0.glb.clouddn.com/head.jpg" class="js-avatar">
			
		</a>

		<hgroup>
		  <h1 class="header-author"><a href="/">sunJsona</a></h1>
		</hgroup>

		

		
			<div class="switch-btn">
				<div class="icon">
					<div class="icon-ctn">
						<div class="icon-wrap icon-house" data-idx="0">
							<div class="birdhouse"></div>
							<div class="birdhouse_holes"></div>
						</div>
						<div class="icon-wrap icon-ribbon hide" data-idx="1">
							<div class="ribbon"></div>
						</div>
						
						<div class="icon-wrap icon-link hide" data-idx="2">
							<div class="loopback_l"></div>
							<div class="loopback_r"></div>
						</div>
						
						
					</div>
					
				</div>
				<div class="tips-box hide">
					<div class="tips-arrow"></div>
					<ul class="tips-inner">
						<li>菜单</li>
						<li>标签</li>
						
						<li>友情链接</li>
						
						
					</ul>
				</div>
			</div>
		

		<div class="switch-area">
			<div class="switch-wrap">
				<section class="switch-part switch-part1">
					<nav class="header-menu">
						<ul>
						
							<li><a href="/">主页</a></li>
				        
							<li><a href="/archives">所有文章</a></li>
				        
						</ul>
					</nav>
					<nav class="header-nav">
						<div class="social">
							
						</div>
					</nav>
				</section>
				
				
				<section class="switch-part switch-part2">
					<div class="widget tagcloud" id="js-tagcloud">
						
					</div>
				</section>
				
				
				
				<section class="switch-part switch-part3">
					<div id="js-friends">
					
			          <a target="_blank" class="main-nav-link switch-friends-link" href="https://github.com/smackgg/hexo-theme-smackdown">smackdown</a>
			        
			        </div>
				</section>
				

				
			</div>
		</div>
	</header>				
</div>
    </div>
    <div class="mid-col">
      <nav id="mobile-nav">
  	<div class="overlay">
  		<div class="slider-trigger"></div>
  		<h1 class="header-author js-mobile-header hide">sunJsona</h1>
  	</div>
	<div class="intrude-less">
		<header id="header" class="inner">
			<div class="profilepic">
				<img lazy-src="http://7xkj1z.com1.z0.glb.clouddn.com/head.jpg" class="js-avatar">
			</div>
			<hgroup>
			  <h1 class="header-author">sunJsona</h1>
			</hgroup>
			
			<nav class="header-menu">
				<ul>
				
					<li><a href="/">主页</a></li>
		        
					<li><a href="/archives">所有文章</a></li>
		        
		        <div class="clearfix"></div>
				</ul>
			</nav>
			<nav class="header-nav">
				<div class="social">
					
				</div>
			</nav>
		</header>				
	</div>
</nav>
      <div class="body-wrap">
  
    <article id="post-分享邀请码功能" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2020/07/07/分享邀请码功能/" class="article-date">
  	<time datetime="2020-07-07T05:00:13.000Z" itemprop="datePublished">2020-07-07</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2020/07/07/分享邀请码功能/">
        分享邀请码功能
        
      </a>
    </h1>
  

      </header>
      
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>分享主要的三个参数：<br>==title==<br>==url==<br>==picurl==</p>
<h5 id="title"><a href="#title" class="headerlink" title="title:"></a>title:</h5><p>即分享到其他平台显示的标题</p>
<h5 id="url："><a href="#url：" class="headerlink" title="url："></a>url：</h5><p>即分享到其他平台的连接</p>
<h5 id="picurl"><a href="#picurl" class="headerlink" title="picurl:"></a>picurl:</h5><p>在其他平台显示的图片</p>
<h4 id="分享到新浪微博"><a href="#分享到新浪微博" class="headerlink" title="分享到新浪微博"></a>分享到新浪微博</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">//分享到新浪微博  </span><br><span class="line">ShareTip.prototype.sharetosina=function(title,url,picurl)  </span><br><span class="line">&#123;  </span><br><span class="line"> var sharesinastring=&apos;http://v.t.sina.com.cn/share/share.php?title=&apos;+title+&apos;&amp;url=&apos;+url+&apos;&amp;content=utf-8&amp;sourceUrl=&apos;+url+&apos;&amp;pic=&apos;+picurl;  </span><br><span class="line"> window.open(sharesinastring,&apos;newwindow&apos;,&apos;height=400,width=400,top=100,left=100&apos;);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="分享到QQ空间"><a href="#分享到QQ空间" class="headerlink" title="分享到QQ空间"></a>分享到QQ空间</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">//分享到QQ空间  </span><br><span class="line">ShareTip.prototype.sharetoqqzone=function(title,url,picurl)  </span><br><span class="line">&#123;  </span><br><span class="line"> var shareqqzonestring=&apos;http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?summary=&apos;+title+&apos;&amp;url=&apos;+url+&apos;&amp;pics=&apos;+picurl+&apos;&amp;title=123456&apos;;  </span><br><span class="line"> window.open(shareqqzonestring,&apos;newwindow&apos;,&apos;height=400,width=400,top=100,left=100&apos;);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="分享到QQ"><a href="#分享到QQ" class="headerlink" title="分享到QQ"></a>分享到QQ</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">//分享到QQ</span><br><span class="line">ShareTip.prototype.sharetoqq=function (title,url,picurl) &#123;</span><br><span class="line">	var shareqqstring = &apos;http://connect.qq.com/widget/shareqq/index.html?url=&apos;+ url +&apos;&amp;pics=&apos;+ picurl +&apos;&amp;site=qiubite&amp;title=&apos; + title;</span><br><span class="line">	window.open(shareqqstring,&apos;newwindow&apos;,&apos;height=500,width=1000,top=100,left=100&apos;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h5 id="复制邀请码"><a href="#复制邀请码" class="headerlink" title="复制邀请码"></a>复制邀请码</h5><ol>
<li><p>选中文本框中的字</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$(codeInput).select();</span><br></pre></td></tr></table></figure>
</li>
<li><p>复制</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">document.execCommand(&quot;Copy&quot;);</span><br><span class="line">		alert(&quot;复制成功！&quot;);</span><br></pre></td></tr></table></figure></li>
</ol>

      
    </div>
    
    <div class="article-info article-info-index">
      
      
      

      
      <div class="clearfix"></div>
    </div>
      
    
  </div>
  
</article>







  
    <article id="post-判断手机系统是ios 还是Android" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2020/07/07/判断手机系统是ios 还是Android/" class="article-date">
  	<time datetime="2020-07-07T05:00:13.000Z" itemprop="datePublished">2020-07-07</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2020/07/07/判断手机系统是ios 还是Android/">
        判断手机系统是ios 还是Android
        
      </a>
    </h1>
  

      </header>
      
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="判断手机系统是ios-还是Android"><a href="#判断手机系统是ios-还是Android" class="headerlink" title="判断手机系统是ios 还是Android"></a>判断手机系统是ios 还是Android</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">var u = navigator.userAgent</span><br><span class="line">var isAndroid = u.indexOf(&apos;Android&apos;) &gt; -1 || u.indexOf(&apos;Adr&apos;) &gt; -1; //android终端</span><br><span class="line">var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端</span><br></pre></td></tr></table></figure>
      
    </div>
    
    <div class="article-info article-info-index">
      
      
      

      
      <div class="clearfix"></div>
    </div>
      
    
  </div>
  
</article>







  
    <article id="post-前端使用 jest 做单元测试" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2020/07/07/前端使用 jest 做单元测试/" class="article-date">
  	<time datetime="2020-07-07T05:00:13.000Z" itemprop="datePublished">2020-07-07</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2020/07/07/前端使用 jest 做单元测试/">
        前端使用 jest 做单元测试
        
      </a>
    </h1>
  

      </header>
      
    
    <div class="article-entry" itemprop="articleBody">
      
        <h3 id="1-安装-Jest"><a href="#1-安装-Jest" class="headerlink" title="1. 安装 Jest"></a>1. 安装 Jest</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install --save-dev jest</span><br></pre></td></tr></table></figure>
<h3 id="2-在script里面加上下面一句："><a href="#2-在script里面加上下面一句：" class="headerlink" title="2. 在script里面加上下面一句："></a>2. 在script里面加上下面一句：</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">/ 添加测试命令</span><br><span class="line">&#123;</span><br><span class="line">  &quot;scripts&quot;: &#123;</span><br><span class="line">    &quot;test&quot;: &quot;jest&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="3-添加被测试文件"><a href="#3-添加被测试文件" class="headerlink" title="3. 添加被测试文件"></a>3. 添加被测试文件</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 被测试文件 sum.js</span><br><span class="line">function sum(a, b) &#123;</span><br><span class="line">  return a + b;</span><br><span class="line">&#125;</span><br><span class="line">module.exports = sum;</span><br></pre></td></tr></table></figure>
<h3 id="4-书写测试文件"><a href="#4-书写测试文件" class="headerlink" title="4. 书写测试文件"></a>4. 书写测试文件</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 测试文件 sum.test.js</span><br><span class="line">const sum = require(‘./sum&apos;);</span><br><span class="line">test(&apos;adds 1 + 2 to equal 3&apos;, () =&gt; &#123;</span><br><span class="line">  expect(sum(1, 2)).toBe(3);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h3 id="5-控制台执行"><a href="#5-控制台执行" class="headerlink" title="5. 控制台执行"></a>5. 控制台执行</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm test</span><br><span class="line">// 或者</span><br><span class="line">npm t</span><br></pre></td></tr></table></figure>
<p><a href="https://blog.csdn.net/u011818572/article/details/81638872" target="_blank" rel="noopener">vue+typescript+jest测试指南</a></p>

      
    </div>
    
    <div class="article-info article-info-index">
      
      
      

      
      <div class="clearfix"></div>
    </div>
      
    
  </div>
  
</article>







  
    <article id="post-在html代码里添加关键字、描述" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2020/07/07/在html代码里添加关键字、描述/" class="article-date">
  	<time datetime="2020-07-07T05:00:13.000Z" itemprop="datePublished">2020-07-07</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2020/07/07/在html代码里添加关键字、描述/">
        在html代码里添加关键字、描述
        
      </a>
    </h1>
  

      </header>
      
    
    <div class="article-entry" itemprop="articleBody">
      
        <h5 id="添加关键字和描述"><a href="#添加关键字和描述" class="headerlink" title="添加关键字和描述"></a>添加关键字和描述</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt;meta name=&quot;keywords&quot; content=&quot;SVG特效, 手机微信网站特效, css3动画, html5特效, 网页特效&quot; /&gt;</span><br><span class="line">&lt;meta name=&quot;description&quot; content=&quot;网页特效库-专注于HTML5、CSS3、js、jQuery、手机移动端等网页特效的手机与分享。特效库始终坚持：无会员、无积分、无限制的“三无原则”，所有的资源都免费提供广大童鞋下载学习和使用。&quot; /&gt;</span><br></pre></td></tr></table></figure>
<h5 id="animation-一直转特效"><a href="#animation-一直转特效" class="headerlink" title="animation 一直转特效"></a>animation 一直转特效</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">@-webkit-keyframes rotate&#123;</span><br><span class="line">    0%&#123;</span><br><span class="line">        transform: rotate(0deg);</span><br><span class="line">    &#125;</span><br><span class="line">    100%&#123;</span><br><span class="line">        transform: rotate(360deg);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">.bagua img&#123;</span><br><span class="line">    width: 548px;</span><br><span class="line">    height: 548px;</span><br><span class="line">    display: block;</span><br><span class="line">    animation:rotate 20s linear 0s infinite;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
      
    </div>
    
    <div class="article-info article-info-index">
      
      
      

      
      <div class="clearfix"></div>
    </div>
      
    
  </div>
  
</article>







  
    <article id="post-实用命令" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2020/07/07/实用命令/" class="article-date">
  	<time datetime="2020-07-07T05:00:13.000Z" itemprop="datePublished">2020-07-07</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
    <div class="article-entry" itemprop="articleBody">
      
        <h5 id="maven打包命令"><a href="#maven打包命令" class="headerlink" title="maven打包命令"></a>maven打包命令</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mvn package -P development</span><br></pre></td></tr></table></figure>

      
    </div>
    
    <div class="article-info article-info-index">
      
      
      

      
      <div class="clearfix"></div>
    </div>
      
    
  </div>
  
</article>







  
    <article id="post-各群主分享的学习资源" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2020/07/07/各群主分享的学习资源/" class="article-date">
  	<time datetime="2020-07-07T05:00:13.000Z" itemprop="datePublished">2020-07-07</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>福利：【群主分享】</p>
<p>开课吧WEB全栈公开课：<a href="https://pan.baidu.com/s/1UdtfzNK3ZN7cDobROHd7Uw，提取码：oil4" target="_blank" rel="noopener">https://pan.baidu.com/s/1UdtfzNK3ZN7cDobROHd7Uw，提取码：oil4</a></p>
<p>珠峰公开课<br>1.最通俗易懂的MVVM原理<br>链接：<a href="https://pan.baidu.com/s/1FidCaXdgO4JX-7dxOhPj3Q" target="_blank" rel="noopener">https://pan.baidu.com/s/1FidCaXdgO4JX-7dxOhPj3Q</a><br>提取码：ibbv<br>2.vue之封装自己的树组件<br>链接：<a href="https://pan.baidu.com/s/1KggzanfhJpkeaED8Ju2O4w" target="_blank" rel="noopener">https://pan.baidu.com/s/1KggzanfhJpkeaED8Ju2O4w</a><br>提取码：x2qs<br>3.Vue单元测试最佳实践<br>链接：<a href="https://pan.baidu.com/s/1Fh0TJTMOxhuKteCamKC9MA" target="_blank" rel="noopener">https://pan.baidu.com/s/1Fh0TJTMOxhuKteCamKC9MA</a><br>提取码：x0hh<br>4.vue之封装自己的日历组件-高级篇<br>链接：<a href="https://pan.baidu.com/s/1bwwjaboe11M78LPX4HaD2w" target="_blank" rel="noopener">https://pan.baidu.com/s/1bwwjaboe11M78LPX4HaD2w</a><br>提取码：j4dp<br>5.从零实现React<br>链接：<a href="https://pan.baidu.com/s/12EGxmIlJE42KBbZF4CAYdA" target="_blank" rel="noopener">https://pan.baidu.com/s/12EGxmIlJE42KBbZF4CAYdA</a><br>提取码：hylw<br>6.jenkin持续集成<br>链接：<a href="https://pan.baidu.com/s/16GBPbUo71RsroughN-AuhQ" target="_blank" rel="noopener">https://pan.baidu.com/s/16GBPbUo71RsroughN-AuhQ</a><br>提取码：t4ht<br>7.从零实现KOA<br>链接：<a href="https://pan.baidu.com/s/1jamrcQ-7aUYHQ63iSSHGTA" target="_blank" rel="noopener">https://pan.baidu.com/s/1jamrcQ-7aUYHQ63iSSHGTA</a><br>提取码：1txi</p>
<p>前端必备资料包链接：<a href="https://pan.baidu.com/s/18kGxZt378pA1zt4duqDAmQ&amp;shfl=shareset" target="_blank" rel="noopener">https://pan.baidu.com/s/18kGxZt378pA1zt4duqDAmQ&amp;shfl=shareset</a><br>提取码: 1xwz</p>
<p>前端电子书：<br>链接:<a href="https://pan.baidu.com/s/1PQqfJDQes58yjS1z3H4Y_A" target="_blank" rel="noopener">https://pan.baidu.com/s/1PQqfJDQes58yjS1z3H4Y_A</a> 提取码:nb3w</p>
<p>珠峰公开课<br>1.最通俗易懂的MVVM原理<br>链接：<a href="https://pan.baidu.com/s/1FidCaXdgO4JX-7dxOhPj3Q" target="_blank" rel="noopener">https://pan.baidu.com/s/1FidCaXdgO4JX-7dxOhPj3Q</a><br>提取码：ibbv<br>2.vue之封装自己的树组件<br>链接：<a href="https://pan.baidu.com/s/1KggzanfhJpkeaED8Ju2O4w" target="_blank" rel="noopener">https://pan.baidu.com/s/1KggzanfhJpkeaED8Ju2O4w</a><br>提取码：x2qs<br>3.Vue单元测试最佳实践<br>链接：<a href="https://pan.baidu.com/s/1Fh0TJTMOxhuKteCamKC9MA" target="_blank" rel="noopener">https://pan.baidu.com/s/1Fh0TJTMOxhuKteCamKC9MA</a><br>提取码：x0hh<br>4.vue之封装自己的日历组件-高级篇<br>链接：<a href="https://pan.baidu.com/s/1bwwjaboe11M78LPX4HaD2w" target="_blank" rel="noopener">https://pan.baidu.com/s/1bwwjaboe11M78LPX4HaD2w</a><br>提取码：j4dp<br>5.从零实现React<br>链接：<a href="https://pan.baidu.com/s/12EGxmIlJE42KBbZF4CAYdA" target="_blank" rel="noopener">https://pan.baidu.com/s/12EGxmIlJE42KBbZF4CAYdA</a><br>提取码：hylw<br>6.jenkin持续集成<br>链接：<a href="https://pan.baidu.com/s/16GBPbUo71RsroughN-AuhQ" target="_blank" rel="noopener">https://pan.baidu.com/s/16GBPbUo71RsroughN-AuhQ</a><br>提取码：t4ht<br>7.从零实现KOA<br>链接：<a href="https://pan.baidu.com/s/1jamrcQ-7aUYHQ63iSSHGTA" target="_blank" rel="noopener">https://pan.baidu.com/s/1jamrcQ-7aUYHQ63iSSHGTA</a><br>提取码：1txi</p>
<p>前端（一）链接：<a href="https://pan.baidu.com/s/1t9fi-eB6BVQIBRpmPtW6Bw" target="_blank" rel="noopener">https://pan.baidu.com/s/1t9fi-eB6BVQIBRpmPtW6Bw</a><br>提取码：PREB </p>
<p>前端（二）链接：<a href="https://pan.baidu.com/s/1h_U9S7EJaZm9xkkYW1dkjg" target="_blank" rel="noopener">https://pan.baidu.com/s/1h_U9S7EJaZm9xkkYW1dkjg</a><br>提取码：b3WV </p>
<p>微信小程序链接：<a href="https://pan.baidu.com/s/10Gs5tLAqF8lQUsVhcsKaTQ" target="_blank" rel="noopener">https://pan.baidu.com/s/10Gs5tLAqF8lQUsVhcsKaTQ</a><br>提取码：53T9 </p>
<p>面试就业指导资料链接：<a href="https://pan.baidu.com/s/1W1HPxuNbsgon_jTVLsFoMw" target="_blank" rel="noopener">https://pan.baidu.com/s/1W1HPxuNbsgon_jTVLsFoMw</a><br>提取码：T03m </p>
<p>2020年前端面试题链接：<a href="https://pan.baidu.com/s/1bDgLjW7nM6hTTtKVF-nzow" target="_blank" rel="noopener">https://pan.baidu.com/s/1bDgLjW7nM6hTTtKVF-nzow</a><br>提取码：X83O </p>

      
    </div>
    
    <div class="article-info article-info-index">
      
      
      

      
      <div class="clearfix"></div>
    </div>
      
    
  </div>
  
</article>







  
    <article id="post-常用的package.json，你不知道的骚技巧" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2020/07/07/常用的package.json，你不知道的骚技巧/" class="article-date">
  	<time datetime="2020-07-07T05:00:13.000Z" itemprop="datePublished">2020-07-07</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="常用的package-json，还有这多你不知道的骚技巧"><a href="#常用的package-json，还有这多你不知道的骚技巧" class="headerlink" title="常用的package.json，还有这多你不知道的骚技巧"></a>常用的package.json，还有这多你不知道的骚技巧</h2><p>前端小黑 <a href="javascript:void(0" target="_blank" rel="noopener">前端梦想家</a>;) <em>今天</em></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/ib46ibiblQLaTHHUa1cpmuicWPBNPJlGhpTccYpB5dY09SLtJcqE4LSiaynFGUCjicoJPdqDFry7Az3krYIjJA4bSk0Q/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="img"></p>
<h2 id="前言-🤔"><a href="#前言-🤔" class="headerlink" title="前言 🤔"></a>前言 🤔</h2><p>在每个项目的根目录下面，一般都会有一个 <code>package.json</code> 文件，其定义了运行项目所需要的各种依赖和项目的配置信息（如名称、版本、许可证等元数据）。</p>
<p>大多数人对 <code>package.json</code> 文件的了解，仅停留在：</p>
<ul>
<li><ul>
<li>项目名称、项目构建版本、许可证的定义；</li>
<li>依赖定义（包括 <code>dependencies</code> 字段，<code>devDependencies</code> 字段）；</li>
<li>使用<code>scripts</code>字段指定运行脚本命令的 <code>npm</code> 命令行缩写。</li>
</ul>
</li>
</ul>
<p>其实，<code>package.json</code> 的作用远不止于此，我们可以通过新增配置项实现更强大的功能，下面将带你重新认识  <code>package.json</code>。</p>
<h2 id="由简入繁，丰富项目的-package-json"><a href="#由简入繁，丰富项目的-package-json" class="headerlink" title="由简入繁，丰富项目的 package.json"></a>由简入繁，丰富项目的 package.json</h2><h3 id="简单版的-package-json"><a href="#简单版的-package-json" class="headerlink" title="简单版的 package.json"></a>简单版的 package.json</h3><p>当我们新建一个名称为 <code>my-test</code> 的项目时，使用 <code>yarn init -y</code> 或 <code>npm init -y</code> 命令后，在项目目录下会新增一个 <code>package.json</code>文件，内容如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">&quot;name&quot;: &quot;my-test&quot;, # 项目名称</span><br><span class="line">&quot;version&quot;: &quot;1.0.0&quot;, # 项目版本（格式：大版本.次要版本.小版本）</span><br><span class="line">&quot;description&quot;: &quot;&quot;, # 项目描述</span><br><span class="line">&quot;main&quot;: &quot;index.js&quot;, # 入口文件</span><br><span class="line">&quot;scripts&quot;: &#123; # 指定运行脚本命令的 npm 命令行缩写</span><br><span class="line">&quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">&quot;keywords&quot;: [], # 关键词</span><br><span class="line">&quot;author&quot;: &quot;&quot;, # 作者</span><br><span class="line">&quot;license&quot;: &quot;ISC&quot; # 许可证</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到，<code>package.json</code> 文件的内容是一个 <code>JSON</code> 对象，对象的每一个成员就是当前项目的一项配置。</p>
<h3 id="必备属性（name-amp-version）"><a href="#必备属性（name-amp-version）" class="headerlink" title="必备属性（name &amp; version）"></a>必备属性（name &amp; version）</h3><ul>
<li><code>package.json</code> 中有非常多的配置项，其中必须填写的两个字段分别是 <code>name</code> 字段和 <code>version</code> 字段，它们是组成一个 <code>npm</code> 模块的唯一标识。</li>
</ul>
<h4 id="name-字段"><a href="#name-字段" class="headerlink" title="name 字段"></a>name 字段</h4><p><code>name</code> 字段定义了模块的名称，其命名时需要遵循官方的一些规范和建议：</p>
<ul>
<li><ul>
<li>模块名会成为模块 <code>url</code>、命令行中的一个参数或者一个文件夹名称，任何非 <code>url</code> 安全的字符在模块名中都不能使用（我们可以使用 <code>validate-npm-package-name</code> 包来检测模块名是否合法）；</li>
<li>语义化模块名，可以帮助开发者更快的找到需要的模块，并且避免意外获取错误的模块；</li>
<li>若模块名称中存在一些符号，将符号去除后不得与现有的模块名重复，例如：由于 <code>react-router-dom</code> 已经存在，<code>react.router.dom</code>、<code>reactrouterdom</code> 都不可以再创建。</li>
</ul>
</li>
</ul>
<p><code>name</code> 字段不能与其他模块名重复，我们可以执行以下命令查看模块名是否已经被使用：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm view &lt;packageName&gt;</span><br></pre></td></tr></table></figure>
<p>如果模块存在，可以查看该模块的一些基本信息：</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/XP4dRIhZqqUQV8Z3TJGIH6Fas9pDtDhZdz15AQSbOGRhWwiaHPR7erLWVLIkAEhppxON6fQ7s3qJGsl5PCIxdag/640?wx_fmt=jpeg&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="img"></p>
<p>如果该模块名从未被使用过，则会抛出 404 错误：</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/XP4dRIhZqqUQV8Z3TJGIH6Fas9pDtDhZjJM9a9pkaghUlWpzV32cZ0k2uaoHd1lgA7jGWXMhlXWMzov3MMCNOA/640?wx_fmt=jpeg&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="img"></p>
<p>或者，我们也可以去 <code>npm</code> 上输入模块名，如果搜不到，则可以使用该模块名。</p>
<h4 id="version-字段"><a href="#version-字段" class="headerlink" title="version 字段"></a>version 字段</h4><p><code>npm</code> 包中的模块版本都需要遵循 <code>SemVer</code> 规范，该规范的标准版本号采用 <code>X.Y.Z</code> 的格式，其中 <code>X</code>、<code>Y</code> 和 <code>Z</code> 均为非负的整数，且禁止在数字前方补零：</p>
<ul>
<li><ul>
<li><code>X</code> 是主版本号(major)：修改了不兼容的 API</li>
<li><code>Y</code> 是次版本号(minor)：新增了向下兼容的功能</li>
<li><code>Z</code> 为修订号(patch)：修正了向下兼容的问题</li>
</ul>
</li>
</ul>
<p>当某个版本改动比较大、并非稳定而且可能无法满足预期的兼容性需求时，我们可能要先发布一个先行版本。</p>
<p>先行版本号可以加到<code>主版本号.次版本号.修订号</code>的后面，通过 <code>-</code> 号连接一连串以句点分隔的标识符和版本编译信息：</p>
<ul>
<li><ul>
<li>内部版本(alpha)</li>
<li>公测版本(beta)</li>
<li>正式版本的候选版本rc（即 Release candiate）</li>
</ul>
</li>
</ul>
<p>我们可以执行以下命令查看模块的版本：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm view &lt;packageName&gt; version # 查看某个模块的最新版本</span><br><span class="line">npm view &lt;packageName&gt; versions # 查看某个模块的所有历史版本</span><br><span class="line">复制代码</span><br></pre></td></tr></table></figure>
<p>查看 <code>antd</code> 的最新版本：<img src="https://mmbiz.qpic.cn/mmbiz_jpg/XP4dRIhZqqUQV8Z3TJGIH6Fas9pDtDhZSTu3002rLOrljRicE48ia9Dv0C2oZDJdNptaJVH84gTiaHzR1Z8IRk4kw/640?wx_fmt=jpeg&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="img"></p>
<p>查看 <code>antd</code> 的所有历史版本：</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/XP4dRIhZqqUQV8Z3TJGIH6Fas9pDtDhZYIvjp4sgSuUrLRKxKzz6WwgiaMIhOrRmWsqkEA9IEwzTFCXywtOZKhA/640?wx_fmt=gif&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt="img"></p>
<p>可以看到，<code>antd</code> 是严格按照 <code>SemVer</code> 规范来发版的，版本号是严格按照<code>主版本号.次版本号.修订号</code>的格式命名和严格递增的，在发布的版本改动较大时，还会先发布<code>alpha</code>、<code>beta</code>、<code>rc</code>等先行版本。</p>
<h3 id="描述信息（description-amp-keywords）"><a href="#描述信息（description-amp-keywords）" class="headerlink" title="描述信息（description &amp; keywords）"></a>描述信息（description &amp; keywords）</h3><ul>
<li><code>description</code> 字段用于添加模块的描述信息，便于用户了解该模块。</li>
<li><code>keywords</code> 字段用于给模块添加关键字。</li>
<li>当我们使用 <code>npm</code> 检索模块时，会对模块中的 <code>description</code> 字段和 <code>keywords</code> 字段进行匹配，写好 <code>package.json</code>中的 <code>description</code> 和 <code>keywords</code> 将有利于增加我们模块的曝光率。</li>
</ul>
<h3 id="安装项目依赖（dependencies-amp-devDependencies）"><a href="#安装项目依赖（dependencies-amp-devDependencies）" class="headerlink" title="安装项目依赖（dependencies &amp; devDependencies）"></a>安装项目依赖（dependencies &amp; devDependencies）</h3><p><code>dependencies</code>字段指定了项目运行所依赖的模块（生产环境使用），如 <code>antd</code>、 <code>react</code>、 <code>moment</code>等插件库：</p>
<ul>
<li><ul>
<li>它们是我们生产环境所需要的依赖项，在把项目作为一个 <code>npm</code> 包的时候，用户安装 <code>npm</code> 包时只会安装 <code>dependencies</code> 里面的依赖。</li>
</ul>
</li>
</ul>
<p><code>devDependencies</code> 字段指定了项目开发所需要的模块（开发环境使用），如 <code>webpack</code>、<code>typescript</code>、<code>babel</code>等：</p>
<ul>
<li><ul>
<li>在代码打包提交线上时，我们并不需要这些工具，所以我们将它放入 <code>devDependencies</code> 中。</li>
</ul>
</li>
</ul>
<p>如果一个模块不在 <code>package.json</code> 文件之中，我们可以单独安装这个模块，并使用相应的参数，将其写入 <code>dependencies</code> 字段/ <code>devDependencies</code> 字段中：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 使用 npm</span><br><span class="line">npm install &lt;package...&gt; --save # 写入 dependencies 属性</span><br><span class="line">npm install &lt;package...&gt; --save-dev # 写入 devDependencies 属性</span><br><span class="line"></span><br><span class="line"># 使用 yarn</span><br><span class="line">yarn add &lt;package...&gt; # 写入 dependencies 属性</span><br><span class="line">yarn add &lt;package...&gt; --dev # 写入 devDependencies 属性</span><br><span class="line">复制代码</span><br></pre></td></tr></table></figure>
<p>有了 <code>package.json</code> 文件，开发直接使用 <code>npm install</code> / <code>yarn install</code> 命令，就会在当前目录中自动安装所需要的模块，安装完成项目所需的运行和开发环境就配置好了。</p>
<h3 id="简化终端命令（scripts）"><a href="#简化终端命令（scripts）" class="headerlink" title="简化终端命令（scripts）"></a>简化终端命令（scripts）</h3><p><code>scripts</code> 字段是 <code>package.json</code> 中的一种元数据功能，它接受一个对象，对象的属性为可以通过 <code>npm run</code>运行的脚本，值为实际运行的命令（通常是终端命令），如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;scripts&quot;: &#123;</span><br><span class="line">&quot;start&quot;: &quot;node index.js&quot;</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>
<p>将终端命令放入 <code>scripts</code> 字段，既可以记录它们又可以实现轻松重用。</p>
<h3 id="定义项目入口（main）"><a href="#定义项目入口（main）" class="headerlink" title="定义项目入口（main）"></a>定义项目入口（main）</h3><p><code>main</code> 字段是 <code>package.json</code> 中的另一种元数据功能，它可以用来指定加载的入口文件。假如你的项目是一个 <code>npm</code> 包，当用户安装你的包后，<code>require(&#39;my-module&#39;)</code> 返回的是 <code>main</code> 字段中所列出文件的 <code>module.exports</code> 属性。</p>
<p>当不指定<code>main</code> 字段时，默认值是模块根目录下面的<code>index.js</code> 文件。</p>
<h3 id="发布文件配置（files）"><a href="#发布文件配置（files）" class="headerlink" title="发布文件配置（files）"></a>发布文件配置（files）</h3><p><code>files</code> 字段用于描述我们使用 <code>npm publish</code> 命令后推送到 <code>npm</code> 服务器的文件列表，如果指定文件夹，则文件夹内的所有内容都会包含进来。</p>
<p>我们可以查看下载的 <code>antd</code> 的 <code>package.json</code> 的<code>files</code> 字段，内容如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&quot;files&quot;: [</span><br><span class="line">&quot;dist&quot;,</span><br><span class="line">&quot;lib&quot;,</span><br><span class="line">&quot;es&quot;</span><br><span class="line">],</span><br></pre></td></tr></table></figure>
<p>可以看到下载后的 <code>antd</code> 包是下面的目录结构：</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/XP4dRIhZqqUQV8Z3TJGIH6Fas9pDtDhZm07FyYHeh30OrsggekZ88BHAe6qpicQD4vDkt1WHWjZBeIhSZuRLYew/640?wx_fmt=jpeg&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="img"></p>
<p>另外，我们还可以通过配置一个 <code>.npmignore</code> 文件来排除一些文件， 防止大量的垃圾文件推送到 <code>npm</code> 上。</p>
<h3 id="定义私有模块（private）"><a href="#定义私有模块（private）" class="headerlink" title="定义私有模块（private）"></a>定义私有模块（private）</h3><p>一般公司的非开源项目，都会设置 <code>private</code> 属性的值为 <code>true</code>，这是因为 <code>npm</code> 拒绝发布私有模块，通过设置该字段可以防止私有模块被无意间发布出去。</p>
<h3 id="指定模块适用系统（os）"><a href="#指定模块适用系统（os）" class="headerlink" title="指定模块适用系统（os）"></a>指定模块适用系统（os）</h3><p>假如我们开发了一个模块，只能跑在 <code>darwin</code> 系统下，我们需要保证 <code>windows</code> 用户不会安装到该模块，从而避免发生不必要的错误。</p>
<p>这时候，使用 <code>os</code> 属性则可以帮助我们实现以上的需求，该属性可以指定模块适用系统的系统，或者指定不能安装的系统黑名单（当在系统黑名单中的系统中安装模块则会报错）：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&quot;os&quot; : [ &quot;darwin&quot;, &quot;linux&quot; ] # 适用系统</span><br><span class="line">&quot;os&quot; : [ &quot;!win32&quot; ] # 黑名单</span><br></pre></td></tr></table></figure>
<blockquote>
<p>Tips：在 <code>node</code> 环境下可以使用 <code>process.platform</code> 来判断操作系统。</p>
</blockquote>
<h3 id="指定模块适用-cpu-架构（cpu）"><a href="#指定模块适用-cpu-架构（cpu）" class="headerlink" title="指定模块适用 cpu 架构（cpu）"></a>指定模块适用 cpu 架构（cpu）</h3><p>和上面的 <code>os</code> 字段类似，我们可以用 <code>cpu</code> 字段更精准的限制用户安装环境：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&quot;cpu&quot; : [ &quot;x64&quot;, &quot;ia32&quot; ] # 适用 cpu</span><br><span class="line">&quot;cpu&quot; : [ &quot;!arm&quot;, &quot;!mips&quot; ] # 黑名单</span><br></pre></td></tr></table></figure>
<blockquote>
<p>Tips：在 <code>node</code> 环境下可以使用 <code>process.arch</code> 来判断 <code>cpu</code> 架构。</p>
</blockquote>
<h3 id="指定项目-node-版本（engines）"><a href="#指定项目-node-版本（engines）" class="headerlink" title="指定项目 node 版本（engines）"></a>指定项目 node 版本（engines）</h3><p>有时候，新拉一个项目的时候，由于和其他开发使用的 <code>node</code> 版本不同，导致会出现很多奇奇怪怪的问题（如某些依赖安装报错、依赖安装完项目跑不起来等）。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/XP4dRIhZqqUQV8Z3TJGIH6Fas9pDtDhZ3IXOcuKe4lswmxkIpwiaia0qgDYbXAFQVVuLzvwmsFfrTyooqnic7LbgQ/640?wx_fmt=jpeg&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="img"></p>
<p>为了实现项目开箱即用的伟大理想，这时候可以使用 <code>package.json</code> 的 <code>engines</code> 字段来指定项目 node 版本：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;engines&quot;: &#123;</span><br><span class="line">&quot;node&quot;: &quot;&gt;= 8.16.0&quot;</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>
<p>该字段也可以指定适用的 <code>npm</code> 版本：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;engines&quot;: &#123;</span><br><span class="line">&quot;npm&quot;: &quot;&gt;= 6.9.0&quot;</span><br><span class="line"> &#125;,</span><br></pre></td></tr></table></figure>
<p>需要注意的是，engines属性仅起到一个说明的作用，当用户版本不符合指定值时也不影响依赖的安装。</p>
<h3 id="自定义命令（bin）"><a href="#自定义命令（bin）" class="headerlink" title="自定义命令（bin）"></a>自定义命令（bin）</h3><p>用过 <code>vue-cli</code>，<code>create-react-app</code>等脚手架的朋友们，不知道你们有没有好奇过，为什么安装这些脚手架后，就可以使用类似 <code>vue create</code>/<code>create-react-app</code>之类的命令，其实这和 <code>package.json</code> 中的 <code>bin</code> 字段有关。</p>
<p><code>bin</code> 字段用来指定各个内部命令对应的可执行文件的位置。当<code>package.json</code> 提供了 <code>bin</code> 字段后，即相当于做了一个命令名和本地文件名的映射。</p>
<p>当用户安装带有 <code>bin</code> 字段的包时，</p>
<ul>
<li><ul>
<li>如果是全局安装，<code>npm</code> 将会使用符号链接把这些文件链接到<code>/usr/local/node_modules/.bin/</code>；</li>
<li>如果是本地安装，会链接到<code>./node_modules/.bin/</code>。</li>
</ul>
</li>
</ul>
<p>举个 🌰，如果要使用 <code>my-app-cli</code> 作为命令时，可以配置以下 <code>bin</code> 字段：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;bin&quot;: &#123;</span><br><span class="line">&quot;my-app-cli&quot;: &quot;./bin/cli.js&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面代码指定，<code>my-app-cli</code> 命令对应的可执行文件为 <code>bin</code> 子目录下的 <code>cli.js</code>，因此在安装了 <code>my-app-cli</code> 包的项目中，就可以很方便地利用 <code>npm</code>执行脚本：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;scripts&quot;: &#123;</span><br><span class="line">  start: &apos;node node_modules/.bin/my-app-cli&apos;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/XP4dRIhZqqUQV8Z3TJGIH6Fas9pDtDhZEiaqlsl4iaJ6icZYO9HKRC9ibtK5lz0eMWneQIWyncjHEOp00iaxPebeGag/640?wx_fmt=jpeg&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="img"></p>
<p>咦，怎么看起来和 <code>vue create</code>/<code>create-react-app</code>之类的命令不太像？原因：</p>
<ul>
<li><ul>
<li>当需要 <code>node</code> 环境时就需要加上 <code>node</code> 前缀</li>
<li>如果加上 <code>node</code> 前缀，就需要指定 <code>my-app-cli</code> 的路径 -&gt; <code>node_modules/.bin</code>，否则 <code>node my-app-cli</code>会去查找当前路径下的 <code>my-app-cli.js</code>，这样肯定是不对。</li>
</ul>
</li>
</ul>
<p>若要实现像 <code>vue create</code>/<code>create-react-app</code>之类的命令一样简便的方式，则可以在上文提到的 <code>bin</code> 子目录下可执行文件<code>cli.js</code> 中的第一行写入以下命令：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#!/usr/bin/env node</span><br></pre></td></tr></table></figure>
<p>这行命令的作用是告诉系统用 <code>node</code> 解析，这样命令就可以简写成 <code>my-app-cli</code> 了。</p>
<h3 id="React-项目相关"><a href="#React-项目相关" class="headerlink" title="React 项目相关"></a>React 项目相关</h3><h4 id="设置应用根路径（homepage）"><a href="#设置应用根路径（homepage）" class="headerlink" title="设置应用根路径（homepage）"></a>设置应用根路径（homepage）</h4><p>当我们使用 <code>create-react-app</code> 脚手架搭建的 <code>React</code> 项目，默认是使用内置的 <code>webpack</code> 配置，当<code>package.json</code> 中不配置 <code>homepage</code> 属性时，build 打包之后的文件资源应用路径默认是  <code>/</code>，如下图：</p>
<p><img src="" alt="img"></p>
<p>一般来说，我们打包的静态资源会部署在 <code>CDN</code> 上，为了让我们的应用知道去哪里加载资源，则需要我们设置一个根路径，这时可以通过 <code>package.json</code> 中的 <code>homepage</code> 字段设置应用的根路径。</p>
<p>当我们设置了 <code>homepage</code> 属性后：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">&quot;homepage&quot;: &quot;https://xxxx.cdn/my-project&quot;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>打包后的资源路径就会加上 <code>homepage</code> 的地址：</p>
<p><img src="" alt="img"></p>
<h4 id="开发环境解决跨域问题（proxy）"><a href="#开发环境解决跨域问题（proxy）" class="headerlink" title="开发环境解决跨域问题（proxy）"></a>开发环境解决跨域问题（proxy）</h4><p>在做前后端分离的项目的时候，调用接口时则会遇到跨域的问题，当在开发环境中时，可以通过配置 <code>package.json</code> 中的 <code>proxy</code> 来解决跨域问题，配置如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">&quot;proxy&quot;: &quot;http://localhost:4000&quot;  // 配置你要请求的服务器地址</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>注意，当 <code>create-react-app</code> 的版本高于 2.0 版本的时候在 <code>package.json</code> 中只能配置 <code>string</code> 类型，这意味着如果要使用 <code>package.json</code> 来解决跨域问题，则只能代理一个服务器地址。</p>
<p>如果要代理多个服务器地址时，则需要安装 <code>http-proxy-middleware</code> ，在 <code>src</code> 目录下新建 <code>setupProxy.js</code> ：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">const proxy = require(&quot;http-proxy-middleware&quot;);</span><br><span class="line"></span><br><span class="line">module.exports = function(app) &#123;</span><br><span class="line">  app.use(</span><br><span class="line">    proxy(&quot;/base&quot;, &#123;</span><br><span class="line">      target: &quot;http://localhost:4000&quot;,</span><br><span class="line">      changeOrigin: true</span><br><span class="line">    &#125;)</span><br><span class="line">  );</span><br><span class="line">  app.use(</span><br><span class="line">    proxy(&quot;/fans&quot;, &#123;</span><br><span class="line">      target: &quot;http://localhost:5000&quot;,</span><br><span class="line">      changeOrigin: true</span><br><span class="line">    &#125;)</span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h4 id="根据开发环境采用不同的全局变量值（自定义字段）"><a href="#根据开发环境采用不同的全局变量值（自定义字段）" class="headerlink" title="根据开发环境采用不同的全局变量值（自定义字段）"></a>根据开发环境采用不同的全局变量值（自定义字段）</h4><p>假设有这么一个组件，当组件被点击时，在开发环境时是跳转测试环境的 <code>sentry</code> 地址，在正式环境时则跳转正式环境的 <code>sentry</code> 地址。</p>
<p>首先，通过配置前面提到的 <code>scripts</code> 字段，实现环境变量（NODE_ENV）的设置：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&quot;scripts&quot;: &#123;</span><br><span class="line">&quot;start&quot;: &quot;NODE_ENV=development node scripts/start.js&quot;,</span><br><span class="line">&quot;build&quot;: &quot;NODE_ENV=production node scripts/build.js&quot;,</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>
<p>项目启动起来后，在代码中我们可以通过 <code>process.env.NODE_ENV</code> 访问到 <code>NODE_ENV</code> 的值。</p>
<h5 id="方案一"><a href="#方案一" class="headerlink" title="方案一"></a>方案一</h5><p>我们可以在组件中写类似以下的判断代码，根据不同环境给 <code>sentryUrl</code> 设置不同的值：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">let sentryUrl;</span><br><span class="line">if (process.env.NODE_ENV === &apos;development&apos;) &#123;</span><br><span class="line">    sentryUrl = &apos;test-sentry.xxx.com&apos;;</span><br><span class="line">&#125; else &#123;</span><br><span class="line">    sentryUrl = &apos;sentry.xxx.com&apos;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这么做好像没毛病，但是深入一想，如果有多个组件，要根据不同的环境使用不同的服务（多种服务）地址，如果按照上面的写法，项目中将存在许多重复的判断代码，且当服务地址发生变化时，包含这些服务地址的组件都需要相应的做改动，这样明显是不合理的。</p>
<p><img src="" alt="img"></p>
<h5 id="方案二"><a href="#方案二" class="headerlink" title="方案二"></a>方案二</h5><p>解决方案：相关服务的地址配置在 <code>package.json</code>中，同时修改项目的 <code>webpack</code> 配置。</p>
<p>注：修改项目的 <code>webpack</code> 配置需要 eject 项目的 <code>webpack</code> 配置（更多细节可阅读 👉：react + typescript 项目的定制化过程）。</p>
<p>在项目根目录下使用 <code>yarn eject</code> 成功 eject 出配置后，可以发现项目目录的变化如下：</p>
<p><img src="" alt="img"></p>
<p>如果需要定制化项目，一般就是在 <code>config</code> 目录下对默认的 <code>webpack</code> 配置进行修改，在这里我们需要关注 <code>config/path.js</code> 和 <code>config/env.js</code> 两个文件：</p>
<ul>
<li><ul>
<li><code>env.js</code> 的主要目的在于读取 <code>env</code> 配置文件并将 <code>env</code> 的配置信息给到全局变量 <code>process.env</code> ；</li>
<li><code>path.js</code> 的主要目的在于为项目提供各种路径，包括构建路径、 <code>public</code> 路径等。</li>
</ul>
</li>
</ul>
<p>由于本文的重点不是学习 <code>webpack</code> 配置，这里仅介绍如何实现【根据开发环境采用不同的全局变量值】的功能。</p>
<p>首先，需要在 <code>package.json</code> 中配置以下内容：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&quot;scripts&quot;: &#123;</span><br><span class="line">&quot;start&quot;: &quot;NODE_ENV=development node scripts/start.js&quot;,</span><br><span class="line">&quot;build&quot;: &quot;NODE_ENV=production node scripts/build.js&quot;,</span><br><span class="line">&#125;,</span><br><span class="line">&quot;sentryPath&quot;: &#123;</span><br><span class="line">&quot;dev&quot;: &quot;https://test-sentry.xxx.com&quot;,</span><br><span class="line">&quot;prod&quot;: &quot;https://sentry.xxx.com&quot;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<p>然后，修改 <code>path.js</code> 文件，内容如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">// 重写 getPublicUrl 方法</span><br><span class="line">const getPublicUrl = (appPackageJson, pathName) =&gt; &#123;</span><br><span class="line">let path;</span><br><span class="line">switch (process.env.DEPLOY_ENV) &#123;</span><br><span class="line">case&apos;development&apos;:</span><br><span class="line">      path = require(appPackageJson)[pathName].dev;</span><br><span class="line">break;</span><br><span class="line">case&apos;production&apos;:</span><br><span class="line">      path = require(appPackageJson)[pathName].prod;</span><br><span class="line">break;</span><br><span class="line">default:</span><br><span class="line">      path = envPublicUrl || require(appPackageJson).homepage;</span><br><span class="line">  &#125;</span><br><span class="line">return path;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// 新增 getSentryPath 方法</span><br><span class="line">const getSentryPath = (appPackageJson) =&gt; &#123;</span><br><span class="line">return getPublicUrl(appPackageJson, &apos;sentryPath&apos;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// config after eject: we&apos;re in ./config/</span><br><span class="line">module.exports = &#123;</span><br><span class="line">  ...,</span><br><span class="line">sentryUrl: getSentryPath(resolveApp(&apos;package.json&apos;)), // 新增</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>最后，修改 <code>env.js</code> 文件，内容如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">// 修改 getClientEnvironment 方法</span><br><span class="line">function getClientEnvironment(publicUrl) &#123;</span><br><span class="line">const raw = Object.keys(process.env)</span><br><span class="line">    .filter(key =&gt; REACT_APP.test(key))</span><br><span class="line">    .reduce(</span><br><span class="line">(env, key) =&gt; &#123;</span><br><span class="line">        ...</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">NODE_ENV: process.env.NODE_ENV || &apos;development&apos;,</span><br><span class="line">PUBLIC_URL: publicUrl,</span><br><span class="line">SENTRY_URL: paths.sentryUrl // 新增</span><br><span class="line">      &#125;</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">const stringified = &#123;</span><br><span class="line">    ...</span><br><span class="line">  &#125;;</span><br><span class="line">return &#123; raw, stringified &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>通过上面的配置，我们就可以在组件中通过 <code>process.env.SENTRY_URL</code> 获取到 <code>sentry</code> 服务的地址了，虽然看起来比方案一繁琐，但是这种收益是长期的，如要新增一个  <code>sonarqube</code> 服务，同理实现即可，通过使用 <code>package.json</code> 也可以清楚的看到当前服务在不同环境下使用的地址。</p>
<h2 id="总结-👀"><a href="#总结-👀" class="headerlink" title="总结 👀"></a>总结 👀</h2><p>本文介绍了 <code>package.json</code> 的多种常见的配置字段及作用，并通过例子加深大家对 <code>package.json</code>这些字段的理解。</p>
<p>除了一些常用字段，还介绍了在<code>React</code> 项目中 <code>package.json</code> 文件能实现的一些功能进行介绍。</p>
<blockquote>
<p>以上内容如有遗漏错误，欢迎留言 ✍️ 指出，一起进步 💪💪💪</p>
</blockquote>
<blockquote>
<p>如果觉得本文对你有帮助，🏀🏀 留下你宝贵的 👍</p>
</blockquote>
<h2 id="参考资料-📖"><a href="#参考资料-📖" class="headerlink" title="参考资料 📖"></a>参考资料 📖</h2><ol>
<li>Creating a package.json file</li>
<li>package.json bin的作用</li>
<li>在开发环境中代理 API 请求</li>
<li>react + typescript 项目的定制化过程</li>
<li>React学习笔记</li>
</ol>
<p><img src="" alt="img"></p>
<p>❤️ 爱心三连击</p>
<p>1.看到这里了就点个在看支持下吧，你的「在看」是我创作的动力。</p>
<p>2.关注公众号前端梦想家，「一起学前端」！</p>
<p>3.添加微信【qdw1370336125】，拉你进技术交流群一起学习。</p>
<p><img src="" alt="img"></p>
<p>在看点这里</p>
<p><img src="" alt="img"></p>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzUyNDA2NTg5NQ==&amp;mid=2247483774&amp;idx=1&amp;sn=4b08fd64305903332bf91dbce5c8f334&amp;chksm=fa324c76cd45c560498645dc358fe78563c102680141669395e82c4ff7bfc46bc6148add56f6&amp;mpshare=1&amp;scene=1&amp;srcid=&amp;sharer_sharetime=1590968877589&amp;sharer_shareid=77ba8debb81295e7116ea85bee64bb55&amp;key=4f01d58ec7196d07e1a6b63018e94327b4cd830a3ce0919a971a5f7163a9e0896564f9e79d7c1d4b3473c32c982b1a82e5db44b6e4a43753b9e782dc93e2337b98fe4cf8e2c2c740817c4eba5fb189cf&amp;ascene=1&amp;uin=NzQ1NDc4NDEz&amp;devicetype=Windows+10+x64&amp;version=62090070&amp;lang=zh_CN&amp;exportkey=A0InUXE8bcG3xlivD2WokMc%3D&amp;pass_ticket=bypY3bZPAuT2pGmEIdf5siU1%2BBXphIvIdFpgwz46qIgh0JUKPgcVYYVNhoAUpQAl&amp;winzoom=1##" target="_blank" rel="noopener">阅读原文</a></p>
<p>阅读 93</p>
<p> 在看8</p>

      
    </div>
    
    <div class="article-info article-info-index">
      
      
      

      
      <div class="clearfix"></div>
    </div>
      
    
  </div>
  
</article>







  
    <article id="post-常用链接" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2020/07/07/常用链接/" class="article-date">
  	<time datetime="2020-07-07T05:00:13.000Z" itemprop="datePublished">2020-07-07</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2020/07/07/常用链接/">
        常用链接
        
      </a>
    </h1>
  

      </header>
      
    
    <div class="article-entry" itemprop="articleBody">
      
        <p><a href="http://start.spring.io/" target="_blank" rel="noopener">http://start.spring.io/</a></p>
<p><a href="http://www.cnblogs.com/lobo/p/5657684.html" target="_blank" rel="noopener">正确、安全地停止SpringBoot应用服务</a></p>
<p><a href="http://www.cnblogs.com/m4tech/p/6610301.html" target="_blank" rel="noopener">Springboot 常用注解</a></p>
<p><a href="http://blog.csdn.net/isea533/article/details/50359390" target="_blank" rel="noopener">Spring Boot 集成MyBatis</a></p>
<p><a href="http://www.cnblogs.com/ityouknow/p/6037431.html" target="_blank" rel="noopener">如何优雅的使用mybatis</a></p>
<p><a href="http://blog.csdn.net/xiaoyu411502/article/details/48049099" target="_blank" rel="noopener">spring boot application properties配置详解</a></p>
<p><a href="http://blog.csdn.net/rainbow702/article/details/55046298" target="_blank" rel="noopener">Spring Boot 不使用默认的 parent，改用自己的项目的 parent</a></p>
<p><a href="http://blog.csdn.net/h363659487/article/details/74955543" target="_blank" rel="noopener">spring-boot-starter-redis配置详解</a></p>
<p><a href="http://blog.csdn.net/lou7441822/article/details/78111307" target="_blank" rel="noopener">spring boot 跨域同源策略filter</a></p>
<p><a href="http://www.cnblogs.com/zhangzhen894095789/p/6640808.html" target="_blank" rel="noopener">SpringBoot的日志管理</a></p>
<p><a href="http://blog.csdn.net/gebitan505/article/details/70142155?locationNum=1&amp;fps=1" target="_blank" rel="noopener">默认日志框架配置</a></p>
<p><a href="http://blog.csdn.net/catoop/article/details/50501696" target="_blank" rel="noopener"> Spring Boot 拦截器</a></p>
<p><a href="http://blog.csdn.net/aqsunkai/article/details/69660817?locationNum=2&amp;fps=1" target="_blank" rel="noopener">整合MyBatis，使用Druid连接池</a></p>
<p><a href="http://blog.csdn.net/github_32521685/article/details/50198467" target="_blank" rel="noopener">Spring Boot自定义错误页面，Whitelabel Error Page处理方式</a></p>
<p><a href="http://blog.csdn.net/wlwlwlwl015/article/details/50246717" target="_blank" rel="noopener"> Mybatis 大数据量的批量insert解决方案</a></p>
<p><a href="http://blog.csdn.net/draven1122/article/details/77505631" target="_blank" rel="noopener">Springboot 集成mybatis并用Logback并打印sql</a></p>
<p><a href="http://blog.csdn.net/u014527058/article/details/62883573" target="_blank" rel="noopener">Spring Boot绑定枚举类型参数</a></p>
<p><a href="http://www.itpub.net/forum.php?mod=viewthread&amp;tid=1133732" target="_blank" rel="noopener">关于数据库表的主键和业务系统中流水号的一点探讨</a></p>
<p><a href="http://blog.csdn.net/catoop/article/details/50501688" target="_blank" rel="noopener">Spring Boot 过滤器、监听器</a></p>
<p><a href="http://blog.csdn.net/u012702547/article/details/53816326" target="_blank" rel="noopener">在Spring Boot框架下使用WebSocket实现消息推送</a></p>
<p><a href="http://blog.csdn.net/sky786905664/article/details/73920815" target="_blank" rel="noopener">vue2 前后端分离项目ajax跨域session问题解决</a></p>
<p><a href="http://blog.csdn.net/linxingliang/article/details/52263763" target="_blank" rel="noopener">Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】</a></p>
<p><a href="http://www.cnblogs.com/ityouknow/p/5748830.html" target="_blank" rel="noopener">spring boot 中redis的使用</a></p>
<p><a href="http://www.cnblogs.com/waterlufei/p/7056420.html" target="_blank" rel="noopener">spring boot + mybatis + druid</a></p>
<p><a href="http://blog.csdn.net/clementad/article/details/51334064" target="_blank" rel="noopener">Eclipse中创建新的Spring Boot项目</a></p>
<p><a href="http://www.css88.com/tool/css3Preview/" target="_blank" rel="noopener">css3样式生成器</a></p>
<p><a href="http://start.spring.io/" target="_blank" rel="noopener">spring boot</a></p>
<p><a href="http://www.pixijs.com/gallery" target="_blank" rel="noopener">pixijs</a></p>
<p><a href="https://1stwebdesigner.com/css-effects/" target="_blank" rel="noopener">css-effects</a></p>
<p><a href="https://github.com/shadowsocks/shadowsocks-windows/wiki/Shadowsocks-Windows-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E" target="_blank" rel="noopener">Shadowsocks</a></p>
<p><a href="http://gitbook.cn/gitchat/vip?utm_source=csdnblog" target="_blank" rel="noopener">gitChat会员</a></p>
<p><a href="http://www.77169.com/" target="_blank" rel="noopener">华盟网</a><br><a href="https://www.bejson.com/" target="_blank" rel="noopener">在线json格式化工具</a></p>
<p><a href="https://haxe.org/" target="_blank" rel="noopener">haxe</a></p>
<p><a href="http://tools.jb51.net/" target="_blank" rel="noopener">脚本之家在线工具</a></p>
<p><a href="http://www.runoob.com/" target="_blank" rel="noopener">脚本之家菜鸟教程</a></p>
<p><a href="http://www.reddit.com/r/programming" target="_blank" rel="noopener">程序员网站 Reddit</a><br><a href="https://plus.google.com/communities" target="_blank" rel="noopener">谷歌社区</a></p>
<p><a href="http://www.sitepoint.com/forums/" target="_blank" rel="noopener">程序员网站 SitePoint</a></p>
<p><a href="https://news.ycombinator.com/news" target="_blank" rel="noopener">程序员网站 Hacker News</a></p>
<hr>
<h2 id="前端"><a href="#前端" class="headerlink" title="前端"></a>前端</h2><p><a href="http://nav.web-hub.cn/" target="_blank" rel="noopener">前端网站导航</a></p>
<p><a href="http://www.henkuai.com/forum.php" target="_blank" rel="noopener">很快 微信小程序社区</a></p>
<p><a href="https://github.com/liutao/vue2.0-source" target="_blank" rel="noopener">VUE 源码解析</a></p>
<p><a href="http://www.alloyteam.com/nav/" target="_blank" rel="noopener">web前端导航</a></p>
<p><a href="http://www.daqianduan.com/" target="_blank" rel="noopener">大前端</a></p>
<p><a href="https://github.com/xswei/d3js_doc" target="_blank" rel="noopener">d3.js中文文档</a></p>
<hr>
<h2 id="设计"><a href="#设计" class="headerlink" title="设计"></a>设计</h2><p><a href="https://www.fontke.com/tool/convfont/" target="_blank" rel="noopener">字客网</a></p>
<p><a href="http://css3ps.com/" target="_blank" rel="noopener">PS 生成css代码插件</a></p>

      
    </div>
    
    <div class="article-info article-info-index">
      
      
      

      
      <div class="clearfix"></div>
    </div>
      
    
  </div>
  
</article>







  
    <article id="post-深入解析 EventLoop 和浏览器渲染、帧动画、空闲回调的关系" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2020/07/07/深入解析 EventLoop 和浏览器渲染、帧动画、空闲回调的关系/" class="article-date">
  	<time datetime="2020-07-07T05:00:13.000Z" itemprop="datePublished">2020-07-07</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="深入解析-EventLoop-和浏览器渲染、帧动画、空闲回调的关系"><a href="#深入解析-EventLoop-和浏览器渲染、帧动画、空闲回调的关系" class="headerlink" title="深入解析 EventLoop 和浏览器渲染、帧动画、空闲回调的关系"></a>深入解析 EventLoop 和浏览器渲染、帧动画、空闲回调的关系</h2><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>关于 Event Loop 的文章很多，但是有很多只是在讲「宏任务」、「微任务」，我先提出几个问题：</p>
<ol>
<li>每一轮 Event Loop 都会伴随着渲染吗？</li>
<li><code>requestAnimationFrame</code> 在哪个阶段执行，在渲染前还是后？在 <code>microTask</code> 的前还是后？</li>
<li><code>requestIdleCallback</code> 在哪个阶段执行？如何去执行？在渲染前还是后？在 <code>microTask</code> 的前还是后？</li>
<li><code>resize</code>、<code>scroll</code> 这些事件是何时去派发的。</li>
</ol>
<p>这些问题并不是刻意想刁难你，如果你不知道这些，那你可能并不能在遇到一个动画需求的时候合理的选择 <code>requestAnimationFrame</code>，你可能在做一些需求的时候想到了 <code>requestIdleCallback</code>，但是你不知道它运行的时机，只是胆战心惊的去用它，祈祷不要出线上 bug。</p>
<p>这也是本文想要从规范解读入手，深挖底层的动机之一。本文会酌情从规范中排除掉一些比较晦涩难懂，或者和主流程不太相关的概念。更详细的版本也可以直接去读这个规范，不过比较费时费力。</p>
<h2 id="事件循环"><a href="#事件循环" class="headerlink" title="事件循环"></a>事件循环</h2><p>我们先依据HTML 官方规范[1]从浏览器的事件循环讲起，因为剩下的 API 都在这个循环中进行，它是浏览器调度任务的基础。</p>
<h3 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h3><p>为了协调事件，用户交互，脚本，渲染，网络任务等，浏览器必须使用本节中描述的事件循环。</p>
<h3 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h3><ol>
<li><p>从任务队列中取出一个<strong>宏任务</strong>并执行。</p>
</li>
<li><p>检查微任务队列，执行并清空<strong>微任务</strong>队列，如果在微任务的执行中又加入了新的微任务，也会在这一步一起执行。</p>
</li>
<li><p>进入更新渲染阶段，判断是否需要渲染，这里有一个 <code>rendering opportunity</code> 的概念，也就是说不一定每一轮 event loop 都会对应一次浏览 器渲染，要根据屏幕刷新率、页面性能、页面是否在后台运行来共同决定，通常来说这个渲染间隔是固定的。（所以多个 task 很可能在一次渲染之间执行）</p>
</li>
<li><ul>
<li>浏览器会尽可能的保持帧率稳定，例如页面性能无法维持 60fps（每 16.66ms 渲染一次）的话，那么浏览器就会选择 30fps 的更新速率，而不是偶尔丢帧。</li>
<li>如果浏览器上下文不可见，那么页面会降低到 4fps 左右甚至更低。</li>
<li>如果满足以下条件，也会跳过渲染：</li>
</ul>
</li>
<li><ol>
<li>浏览器判断更新渲染不会带来视觉上的改变。</li>
<li><code>map of animation frame callbacks</code> 为空，也就是帧动画回调为空，可以通过 <code>requestAnimationFrame</code> 来请求帧动画。</li>
</ol>
</li>
<li><p>如果上述的判断决定本轮<strong>不需要渲染</strong>，那么<strong>下面的几步也不会继续运行</strong>：</p>
<blockquote>
<p>This step enables the user agent to prevent the steps below from running for other reasons, for example, to ensure certain tasks are executed immediately after each other, with only microtask checkpoints interleaved (and without, e.g., animation frame callbacks interleaved). Concretely, a user agent might wish to coalesce timer callbacks together, with no intermediate rendering updates. 有时候浏览器希望两次「定时器任务」是合并的，他们之间只会穿插着 <code>microTask</code>的执行，而不会穿插屏幕渲染相关的流程（比如<code>requestAnimationFrame</code>，下面会写一个例子）。</p>
</blockquote>
</li>
<li><p>对于需要渲染的文档，如果窗口的大小发生了变化，执行监听的 <code>resize</code> 方法。</p>
</li>
<li><p>对于需要渲染的文档，如果页面发生了滚动，执行 <code>scroll</code> 方法。</p>
</li>
<li><p>对于需要渲染的文档，执行帧动画回调，也就是 <strong><code>requestAnimationFrame</code></strong> 的回调。（后文会详解）</p>
</li>
<li><p>对于需要渲染的文档， 执行 IntersectionObserver 的回调。</p>
</li>
<li><p>对于需要渲染的文档，<strong>重新渲染</strong>绘制用户界面。</p>
</li>
<li><p>判断 <code>task队列</code>和<code>microTask</code>队列是否都为空，如果是的话，则进行 <code>Idle</code> 空闲周期的算法，判断是否要执行 <strong><code>requestIdleCallback</code></strong> 的回调函数。（后文会详解）</p>
</li>
</ol>
<p>对于<code>resize</code> 和 <code>scroll</code>来说，并不是到了这一步才去执行滚动和缩放，那岂不是要延迟很多？浏览器当然会立刻帮你滚动视图，根据CSSOM 规范[2]所讲，浏览器会保存一个 <code>pending scroll event targets</code>，等到事件循环中的 <code>scroll</code>这一步，去派发一个事件到对应的目标上，驱动它去执行监听的回调函数而已。<code>resize</code>也是同理。</p>
<p>可以在这个流程中仔细看一下「宏任务」、「微任务」、「渲染」之间的关系。</p>
<h3 id="多任务队列"><a href="#多任务队列" class="headerlink" title="多任务队列"></a>多任务队列</h3><p><code>task</code> 队列并不是我们想象中的那样只有一个，根据规范里的描述：</p>
<blockquote>
<p>An event loop has one or more task queues. For example, a user agent could have one task queue for mouse and key events (to which the user interaction task source is associated), and another to which all other task sources are associated. Then, using the freedom granted in the initial step of the event loop processing model, it could give keyboard and mouse events preference over other tasks three-quarters of the time, keeping the interface responsive but not starving other task queues. Note that in this setup, the processing model still enforces that the user agent would never process events from any one task source out of order.</p>
</blockquote>
<p>事件循环中可能会有<strong>一个或多个</strong>任务队列，这些队列分别为了处理：</p>
<ol>
<li>鼠标和键盘事件</li>
<li>其他的一些 Task</li>
</ol>
<p>浏览器会在保持任务顺序的前提下，可能分配四分之三的优先权给鼠标和键盘事件，保证用户的输入得到最高优先级的响应，而剩下的优先级交给其他 <code>Task</code>，并且保证不会“饿死”它们。</p>
<p>这个规范也导致 Vue 2.0.0-rc.7 这个版本 <code>nextTick</code> 采用了从微任务 <code>MutationObserver</code>更换成宏任务 <code>postMessage</code> 而导致了一个 Issue[3]。</p>
<p>目前由于一些“未知”的原因，jsfiddle 的案例打不开了。简单描述一下就是采用了 <code>task</code> 实现的 <code>nextTick</code>，在用户持续滚动的情况下 <code>nextTick</code> 任务被延后了很久才去执行，导致动画跟不上滚动了。</p>
<p>迫于无奈，尤大还是改回了 <code>microTask</code> 去实现 <code>nextTick</code>，当然目前来说 <code>promise.then</code>微任务已经比较稳定了，并且 Chrome 也已经实现了 <code>queueMicroTask</code> 这个官方 API。不久的未来，我们想要调用微任务队列的话，也可以节省掉实例化 <code>Promise</code> 在开销了。</p>
<p>从这个 Issue 的例子中我们可以看出，稍微去深入了解一下规范还是比较有好处的，以免在遇到这种比较复杂的 Bug 的时候一脸懵逼。</p>
<p>下面的章节中咱们来详细聊聊 <code>requestIdleCallback</code> 和 <code>requestAnimationFrame</code>。</p>
<h2 id="requestAnimationFrame"><a href="#requestAnimationFrame" class="headerlink" title="requestAnimationFrame"></a>requestAnimationFrame</h2><blockquote>
<p>以下内容中 <code>requestAnimationFrame</code>简称为<code>rAF</code></p>
</blockquote>
<p>在解读规范的过程中，我们发现 <code>requestAnimationFrame</code> 的回调有两个特征：</p>
<ol>
<li>在重新渲染前调用。</li>
<li>很可能在宏任务之后不调用。</li>
</ol>
<p>我们来分析一下，为什么要在重新渲染前去调用？因为 <code>rAF</code> 是官方推荐的用来做一些流畅动画所应该使用的 API，做动画不可避免的会去更改 DOM，而如果在渲染之后再去更改 DOM，那就只能等到下一轮渲染机会的时候才能去绘制出来了，这显然是不合理的。</p>
<p><code>rAF</code>在浏览器决定渲染之前给你最后一个机会去改变 DOM 属性，然后很快在接下来的绘制中帮你呈现出来，所以这是做流畅动画的不二选择。下面我用一个 <code>setTimeout</code>的例子来对比。</p>
<h3 id="闪烁动画"><a href="#闪烁动画" class="headerlink" title="闪烁动画"></a>闪烁动画</h3><p>假设我们现在想要快速的让屏幕上闪烁 <code>红</code>、<code>蓝</code>两种颜色，保证用户可以观察到，如果我们用 <code>setTimeout</code> 来写，并且带着我们长期的误解「宏任务之间一定会伴随着浏览器绘制」，那么你会得到一个预料之外的结果。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">setTimeout(() =&gt; &#123;</span><br><span class="line">  document.body.style.background = &quot;red&quot;</span><br><span class="line">  setTimeout(() =&gt; &#123;</span><br><span class="line">    document.body.style.background = &quot;blue&quot;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/iagNW4Zy9CybCeaJbFaSAcXDDs0DzRf1ibM3WgUtKk03CtgDxSVJTj7MBWJ4DialoCRSKSBBZ9TlF2LcFW0X1cA7Q/640?wx_fmt=gif&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt="img"></p>
<p>可以看出这个结果是非常不可控的，如果这两个 <code>Task</code> 之间正好遇到了浏览器认定的渲染机会，那么它会重绘，否则就不会。由于这俩宏任务的间隔周期太短了，所以很大概率是不会的。</p>
<p>如果你把延时调整到 <code>17ms</code> 那么重绘的概率会大很多，毕竟这个是一般情况下 <code>60fps</code> 的一个指标。但是也会出现很多不绘制的情况，所以并不稳定。</p>
<p>如果你依赖这个 API 来做动画，那么就很可能会造成「掉帧」。</p>
<p>接下来我们换成 <code>rAF</code> 试试？我们用一个递归函数来模拟 10 次颜色变化的动画。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">let i = 10</span><br><span class="line">let req = () =&gt; &#123;</span><br><span class="line">  i--</span><br><span class="line">  requestAnimationFrame(() =&gt; &#123;</span><br><span class="line">    document.body.style.background = &quot;red&quot;</span><br><span class="line">    requestAnimationFrame(() =&gt; &#123;</span><br><span class="line">      document.body.style.background = &quot;blue&quot;</span><br><span class="line">      if (i &gt; 0) &#123;</span><br><span class="line">        req()</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">req()</span><br></pre></td></tr></table></figure>
<p>这里由于颜色变化太快，<code>gif</code> 录制软件没办法截出这么高帧率的颜色变换，所以各位可以放到浏览器中自己执行一下试试，我这边直接抛结论，浏览器会非常规律的把这 10 组也就是 20 次颜色变化绘制出来，可以看下 performance 面板记录的表现：</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/iagNW4Zy9CybCeaJbFaSAcXDDs0DzRf1ibMUJibHdJ6GBkePDg4Zkey2LzaBv1gKAu5AgBPyHAOLlU5eOeia0iaYkUg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="img"></p>
<h3 id="定时器合并"><a href="#定时器合并" class="headerlink" title="定时器合并"></a>定时器合并</h3><p>在第一节解读规范的时候，第 4 点中提到了，定时器宏任务可能会直接跳过渲染。</p>
<p>按照一些常规的理解来说，宏任务之间理应穿插渲染，而定时器任务就是一个典型的宏任务，看一下以下的代码：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">setTimeout(() =&gt; &#123;</span><br><span class="line">  console.log(&quot;sto&quot;)</span><br><span class="line">  requestAnimationFrame(() =&gt; console.log(&quot;rAF&quot;))</span><br><span class="line">&#125;)</span><br><span class="line">setTimeout(() =&gt; &#123;</span><br><span class="line">  console.log(&quot;sto&quot;)</span><br><span class="line">  requestAnimationFrame(() =&gt; console.log(&quot;rAF&quot;))</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">queueMicrotask(() =&gt; console.log(&quot;mic&quot;))</span><br><span class="line">queueMicrotask(() =&gt; console.log(&quot;mic&quot;))</span><br></pre></td></tr></table></figure>
<p>从直觉上来看，顺序是不是应该是：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">mic</span><br><span class="line">mic</span><br><span class="line">sto</span><br><span class="line">rAF</span><br><span class="line">sto</span><br><span class="line">rAF</span><br></pre></td></tr></table></figure>
<p>呢？也就是每一个宏任务之后都紧跟着一次渲染。</p>
<p>实际上不会，浏览器会合并这两个定时器任务：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">mic</span><br><span class="line">mic</span><br><span class="line">sto</span><br><span class="line">sto</span><br><span class="line">rAF</span><br><span class="line">rAF</span><br></pre></td></tr></table></figure>
<h2 id="requestIdleCallback"><a href="#requestIdleCallback" class="headerlink" title="requestIdleCallback"></a>requestIdleCallback</h2><h3 id="草案解读"><a href="#草案解读" class="headerlink" title="草案解读"></a>草案解读</h3><blockquote>
<p>以下内容中 <code>requestIdleCallback</code>简称为<code>rIC</code>。</p>
</blockquote>
<p>我们都知道 <code>requestIdleCallback</code> 是浏览器提供给我们的空闲调度算法，关于它的简介可以看 MDN 文档[4]，意图是让我们把一些计算量较大但是又没那么紧急的任务放到空闲时间去执行。不要去影响浏览器中优先级较高的任务，比如动画绘制、用户输入等等。</p>
<p>React 的时间分片渲染就想要用到这个 API，不过目前浏览器支持的不给力，他们是自己去用 <code>postMessage</code> 实现了一套。</p>
<h4 id="渲染有序进行"><a href="#渲染有序进行" class="headerlink" title="渲染有序进行"></a>渲染有序进行</h4><p>首先看一张图，很精确的描述了这个 API 的意图：</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/iagNW4Zy9CybCeaJbFaSAcXDDs0DzRf1ibH1iagpF02l9Llib2gVUfdtQJDOPgKSUs6Aia8Wyr2Yibz2zQqmvF6nic1xg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="img"></p>
<p>当然，这种有序的 <code>浏览器 -&gt; 用户 -&gt; 浏览器 -&gt; 用户</code> 的调度基于一个前提，就是我们要把任务切分成比较小的片，不能说浏览器把空闲时间让给你了，你去执行一个耗时 <code>10s</code> 的任务，那肯定也会把浏览器给阻塞住的。这就要求我们去读取 <code>rIC</code> 提供给你的 <code>deadline</code> 里的时间，去动态的安排我们切分的小任务。浏览器信任了你，你也不能辜负它呀。</p>
<h4 id="渲染长期空闲"><a href="#渲染长期空闲" class="headerlink" title="渲染长期空闲"></a>渲染长期空闲</h4><p><img src="https://mmbiz.qpic.cn/mmbiz_png/iagNW4Zy9CybCeaJbFaSAcXDDs0DzRf1ibSlHUmibZcMKaylLsRc98Qrx97E4QB2wicTRsW43qCYSQGEcFYERiaF0sA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="img">还有一种情况，也有可能在几帧的时间内浏览器都是空闲的，并没有发生任何影响视图的操作，它也就不需要去绘制页面：这种情况下为什么还是会有 <code>50ms</code> 的 <code>deadline</code> 呢？是因为浏览器为了提前应对一些可能会突发的用户交互操作，比如用户输入文字。如果给的时间太长了，你的任务把主线程卡住了，那么用户的交互就得不到回应了。50ms 可以确保用户在无感知的延迟下得到回应。</p>
<p>MDN 文档中的幕后任务协作调度 API [5] 介绍的比较清楚，来根据里面的概念做个小实验：</p>
<p>屏幕中间有个红色的方块，把 MDN 文档中requestAnimationFrame[6]的范例部分的动画代码直接复制过来。</p>
<p>草案中还提到：</p>
<ol>
<li>当浏览器判断这个页面对用户不可见时，这个回调执行的频率可能被降低到 10 秒执行一次，甚至更低。这点在解读 EventLoop 中也有提及。</li>
<li>如果浏览器的工作比较繁忙的时候，不能保证它会提供空闲时间去执行 <code>rIC</code> 的回调，而且可能会长期的推迟下去。所以如果你需要保证你的任务在一定时间内一定要执行掉，那么你可以给 <code>rIC</code> 传入第二个参数 <code>timeout</code>。<br>这会强制浏览器不管多忙，都在超过这个时间之后去执行 <code>rIC</code> 的回调函数。所以要谨慎使用，因为它会打断浏览器本身优先级更高的工作。</li>
<li>最长期限为 50 毫秒，是根据研究得出的，研究表明，人们通常认为 100 毫秒内对用户输入的响应是瞬时的。将闲置截止期限设置为 50ms 意味着即使在闲置任务开始后立即发生用户输入，浏览器仍然有剩余的 50ms 可以在其中响应用户输入而不会产生用户可察觉的滞后。</li>
<li>每次调用 <code>timeRemaining()</code> 函数判断是否有剩余时间的时候，如果浏览器判断此时有优先级更高的任务，那么会动态的把这个值设置为 0，否则就是用预先设置好的 <code>deadline - now</code> 去计算。</li>
<li>这个 <code>timeRemaining()</code> 的计算非常动态，会根据很多因素去决定，所以不要指望这个时间是稳定的。</li>
</ol>
<h3 id="动画例子"><a href="#动画例子" class="headerlink" title="动画例子"></a>动画例子</h3><h4 id="滚动"><a href="#滚动" class="headerlink" title="滚动"></a>滚动</h4><p>如果我鼠标不做任何动作和交互，直接在控制台通过 <code>rIC</code> 去打印这次空闲任务的剩余时间，一般都稳定维持在 <code>49.xx</code> ms，因为此时浏览器没有什么优先级更高的任务要去处理。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/iagNW4Zy9CybCeaJbFaSAcXDDs0DzRf1ibY0FBe5Xv45BwibckaEicIPibqoJ71oHHpKZc69uQpdgRZnHfXibq4UpRLA/640?wx_fmt=gif&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt="img"></p>
<p>而如果我不停的滚动浏览器，不断的触发浏览器的重新绘制的话，这个时间就变的非常不稳定了。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/iagNW4Zy9CybCeaJbFaSAcXDDs0DzRf1ib3CMT4Pzibr0agmOHFfQ1Br6J5XWWyoR1mZULRxINMibtjcZDyUNjfbrA/640?wx_fmt=gif&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt="img"></p>
<p>通过这个例子，你可以更加有体感的感受到什么样叫做「繁忙」，什么样叫做「空闲」。</p>
<h4 id="动画"><a href="#动画" class="headerlink" title="动画"></a>动画</h4><p>这个动画的例子很简单，就是利用<code>rAF</code>在每帧渲染前的回调中把方块的位置向右移动 10px。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html lang=&quot;en&quot;&gt;</span><br><span class="line">  &lt;head&gt;</span><br><span class="line">    &lt;meta charset=&quot;UTF-8&quot; /&gt;</span><br><span class="line">    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;</span><br><span class="line">    &lt;title&gt;Document&lt;/title&gt;</span><br><span class="line">    &lt;style&gt;</span><br><span class="line">      #SomeElementYouWantToAnimate &#123;</span><br><span class="line">        height: 200px;</span><br><span class="line">        width: 200px;</span><br><span class="line">        background: red;</span><br><span class="line">      &#125;</span><br><span class="line">    &lt;/style&gt;</span><br><span class="line">  &lt;/head&gt;</span><br><span class="line">  &lt;body&gt;</span><br><span class="line">    &lt;div id=&quot;SomeElementYouWantToAnimate&quot;&gt;&lt;/div&gt;</span><br><span class="line">    &lt;script&gt;</span><br><span class="line">      var start = null</span><br><span class="line">      var element = document.getElementById(&quot;SomeElementYouWantToAnimate&quot;)</span><br><span class="line">      element.style.position = &quot;absolute&quot;</span><br><span class="line"></span><br><span class="line">      function step(timestamp) &#123;</span><br><span class="line">        if (!start) start = timestamp</span><br><span class="line">        var progress = timestamp - start</span><br><span class="line">        element.style.left = Math.min(progress / 10, 200) + &quot;px&quot;</span><br><span class="line">        if (progress &lt; 2000) &#123;</span><br><span class="line">          window.requestAnimationFrame(step)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      // 动画</span><br><span class="line">      window.requestAnimationFrame(step)</span><br><span class="line"></span><br><span class="line">      // 空闲调度</span><br><span class="line">      window.requestIdleCallback(() =&gt; &#123;</span><br><span class="line">        alert(&quot;rIC&quot;)</span><br><span class="line">      &#125;)</span><br><span class="line">    &lt;/script&gt;</span><br><span class="line">  &lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure>
<p>注意在最后我加了一个 <code>requestIdleCallback</code> 的函数，回调里会 <code>alert(&#39;rIC&#39;)</code>，来看一下演示效果：</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/iagNW4Zy9CybCeaJbFaSAcXDDs0DzRf1ibJn8bJ3kicFiaL3EfFqrkfEr7ALSkDSZ86iblgh9w2ShDzSCXZAYVj0YsA/640?wx_fmt=gif&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt="img"></p>
<p><code>alert</code> 在最开始的时候就执行了，为什么会这样呢一下，想一下「空闲」的概念，我们每一帧仅仅是把 <code>left</code> 的值移动了一下，做了这一个简单的渲染，没有占满空闲时间，所以可能在最开始的时候，浏览器就找到机会去调用 <code>rIC</code> 的回调函数了。</p>
<p>我们简单的修改一下 <code>step</code> 函数，在里面加一个很重的任务，1000 次循环打印。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">function step(timestamp) &#123;</span><br><span class="line">  if (!start) start = timestamp</span><br><span class="line">  var progress = timestamp - start</span><br><span class="line">  element.style.left = Math.min(progress / 10, 200) + &quot;px&quot;</span><br><span class="line">  let i = 1000</span><br><span class="line">  while (i &gt; 0) &#123;</span><br><span class="line">    console.log(&quot;i&quot;, i)</span><br><span class="line">    i--</span><br><span class="line">  &#125;</span><br><span class="line">  if (progress &lt; 2000) &#123;</span><br><span class="line">    window.requestAnimationFrame(step)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>再来看一下它的表现：</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/iagNW4Zy9CybCeaJbFaSAcXDDs0DzRf1iblAbPkmGQA5dqcZx9zoEM7q6Tq2FQf3404JvKSVRJuoWUPkibWfyNg9A/640?wx_fmt=gif&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt="img"></p>
<p>其实和我们预期的一样，由于浏览器的每一帧都”太忙了”,导致它真的就无视我们的 <code>rIC</code> 函数了。</p>
<p>如果给 <code>rIC</code> 函数加一个 <code>timeout</code> 呢：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// 空闲调度</span><br><span class="line">window.requestIdleCallback(</span><br><span class="line">  () =&gt; &#123;</span><br><span class="line">    alert(&quot;rID&quot;)</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123; timeout: 500 &#125;,</span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/iagNW4Zy9CybCeaJbFaSAcXDDs0DzRf1ibnV3ukjqDOVKM7jlRTX9AicjEia4BP9ZxmLDO1atUJS36YUbKGlXWDUfA/640?wx_fmt=gif&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt="img"></p>
<p>浏览器会在大概 <code>500ms</code> 的时候，不管有多忙，都去强制执行 <code>rIC</code> 函数，这个机制可以防止我们的空闲任务被“饿死”。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过本文的学习过程，我自己也打破了很多对于 Event Loop 以及 rAF、rIC 函数的固有错误认知，通过本文我们可以整理出以下的几个关键点。</p>
<ol>
<li>事件循环不一定每轮都伴随着重渲染，但是一定会伴随着微任务执行。</li>
<li>决定浏览器视图是否渲染的因素很多，浏览器是非常聪明的。</li>
<li><code>requestAnimationFrame</code>在重新渲染屏幕之前执行，非常适合用来做动画。</li>
<li><code>requestIdleCallback</code>在渲染屏幕之后执行，并且是否有空执行要看浏览器的调度，如果你一定要它在某个时间内执行，请使用 <code>timeout</code>参数。</li>
<li><code>resize</code>和<code>scroll</code>事件其实自带节流，它只在 Event Loop 的渲染阶段去执行事件。</li>
</ol>
<p>另外，本文也是对于规范的解读，规范里的一些术语比较晦涩难懂，所以我也结合了一些自己的理解去写这篇文章，如果有错误的地方欢迎各位小伙伴指出。</p>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>HTML 规范文档[7]</p>
<p>W3C 标准[8]</p>
<p>Vue 源码详解之 nextTick：MutationObserver 只是浮云，microtask 才是核心！[9]（强烈推荐这篇文章）</p>

      
    </div>
    
    <div class="article-info article-info-index">
      
      
      

      
      <div class="clearfix"></div>
    </div>
      
    
  </div>
  
</article>







  
    <article id="post-用django创建一个项目" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/2020/07/07/用django创建一个项目/" class="article-date">
  	<time datetime="2020-07-07T05:00:13.000Z" itemprop="datePublished">2020-07-07</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2020/07/07/用django创建一个项目/">
        用django创建一个项目
        
      </a>
    </h1>
  

      </header>
      
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>首先你得安装好python和django，然后配置好环境变量，安装python就不说了，从配置环境变量开始</p>
<h5 id="1-配置环境变量"><a href="#1-配置环境变量" class="headerlink" title="1.配置环境变量"></a>1.配置环境变量</h5><p>在我的电脑处点击右键，或者打开 控制面板\系统和安全\系统 -&gt;</p>
<p>左边导航栏的“高级系统设置”-&gt;环境变量 –&gt;然后你会看到下面这个界面</p>
<p><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103195539128-1894247265.png" alt=""><br>-&gt; 点击这个path，然后点编辑</p>
<p>然后找到C:\Python27文件夹，将这个文件夹添加进去。</p>
<h5 id="2-安装django"><a href="#2-安装django" class="headerlink" title="2.安装django"></a>2.安装django</h5><p>打开cmd，执行 pip install django 或者 把<img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103195951191-1293610516.png" alt=""><br>这个包下载下来，然后解压之后打开，是这样的</p>
<p><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103200123878-1846022323.png" alt=""><br>-&gt;打开cmd ,打开这个文件夹,执行 <code>python setup.py install</code><br>等着就行了</p>
<h5 id="3-检验django是否安装成功"><a href="#3-检验django是否安装成功" class="headerlink" title="3. 检验django是否安装成功"></a>3. 检验django是否安装成功</h5><p><code>import django</code><br>如果这句话不报错的话，就没什么问题了。</p>
<h5 id="4-配置django环境变质量。"><a href="#4-配置django环境变质量。" class="headerlink" title="4. 配置django环境变质量。"></a>4. 配置django环境变质量。</h5><p>还是像刚才那样打开环境变量里的path，这次添加的路径是：<img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103200738237-1012230503.png" alt=""></p>
<ol start="5">
<li>用django创建项目</li>
</ol>
<p>打开cmd，并进入你的工作空间，执行命令：<br><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103200951722-394835961.png" alt=""></p>
<p>如果你看到的是下面这样的话，<br><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103201030722-218731608.png" alt=""></p>
<p>这可能是版本问题导致的，你可以执行<br><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103201118253-933553388.png" alt=""></p>
<p>如果没有任何提示，就是成功了。如果还报错，就得检查检查了。</p>
<p>执行成功之后，在你的工作空间目录会有两层website文件夹，进入到两层website文件夹下，会有以下几个文件</p>
<p><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103202329550-1021831256.png" alt=""></p>
<h5 id="6-创建djangoapp"><a href="#6-创建djangoapp" class="headerlink" title="6. 创建djangoapp"></a>6. 创建djangoapp</h5><p>进入website目录下，执行 <code>admin.py startapp blog</code> 或者 <code>admin startapp blog</code><br><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103202532987-734281570.png" alt=""></p>
<p>这里还是没有什么提示，这时候在你的website目录下就多了一个blog文件夹，和一个自带的sqlite3数据库。<br><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103202638644-219210958.png" alt=""></p>
<h5 id="7-修改-urls-py-和-views-py"><a href="#7-修改-urls-py-和-views-py" class="headerlink" title="7.修改 urls.py 和 views.py"></a>7.修改 urls.py 和 views.py</h5><p>打开 views.py 加入测试代码如下：<br><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103203359566-894259538.png" alt=""></p>
<p>其实就是定义了一个函数，然后返回一个字符串。<br>打开 urls.py ，添加一句话<br><code>url(r&#39;^blog/index/$&#39;,blog.views.index,name=&#39;index&#39;)</code></p>
<p><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103203233878-325335233.png" alt=""></p>
<h5 id="8-开启服务器"><a href="#8-开启服务器" class="headerlink" title="8. 开启服务器"></a>8. 开启服务器</h5><p>在website目录下运行命令:<br><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103203550237-1673257793.png" alt=""></p>
<p>或者 <code>manage.py runserver</code> ，版本不一样导致<br>如果你看到下面这个界面，就代表服务器开启成功了<br><img src="https://sunchang.oss-cn-beijing.aliyuncs.com/image/828578-20170103203725644-2085813319.png" alt=""></p>
<p>你可以打开浏览器，输入 <code>127.0.0.0:8000</code></p>
<p>这个地址，查看结果。</p>

      
    </div>
    
    <div class="article-info article-info-index">
      
      
      

      
      <div class="clearfix"></div>
    </div>
      
    
  </div>
  
</article>







  
  
    <nav id="page-nav">
      <a class="extend prev" rel="prev" href="/page/3/">&laquo; Prev</a><a class="page-number" href="/">1</a><a class="page-number" href="/page/2/">2</a><a class="page-number" href="/page/3/">3</a><span class="page-number current">4</span><a class="page-number" href="/page/5/">5</a><a class="extend next" rel="next" href="/page/5/">Next &raquo;</a>
    </nav>
  
</div>
      <footer id="footer">
  <div class="outer">
    <div id="footer-info">
      <div class="footer-left">
        &copy; 2022 sunJsona
      </div>
        <div class="footer-right">
          <a href="http://hexo.io/" target="_blank">Hexo</a>  Theme <a href="https://github.com/smackgg/hexo-theme-smackdown" target="_blank">Smackdown</a>
        </div>
    </div>
  </div>
</footer>
    </div>
    
  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css">


<script>
	var yiliaConfig = {
		fancybox: true,
		mathjax: true,
		animate: true,
		isHome: true,
		isPost: false,
		isArchive: false,
		isTag: false,
		isCategory: false,
		open_in_new: false
	}
</script>
<script src="/js/main.js"></script>



<script type="text/x-mathjax-config">
MathJax.Hub.Config({
    tex2jax: {
        inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
        processEscapes: true,
        skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
    }
});

MathJax.Hub.Queue(function() {
    var all = MathJax.Hub.getAllJax(), i;
    for(i=0; i < all.length; i += 1) {
        all[i].SourceElement().parentNode.className += ' has-jax';                 
    }       
});
</script>

<script src="//cdn.bootcss.com/mathjax/2.7.0/MathJax.js"></script>


  </div>
</body>
</html>